#ifdef USE_MBED_TLS

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>


#include "mbedtls/config.h"
#include "mbedtls/debug.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/error.h"
#include "mbedtls/certs.h"
#include "mbedtls/entropy_poll.h"

#include "azure_c_shared_utility/optimize_size.h"
#include "azure_c_shared_utility/tlsio.h"
#include "azure_c_shared_utility/tlsio_mbedtls.h"
#include "azure_c_shared_utility/crt_abstractions.h"
#include "azure_c_shared_utility/shared_util_options.h"
#include "azure_c_shared_utility/singlylinkedlist.h"
#include "azure_c_shared_utility/optionhandler.h"
#include "azure_c_shared_utility/tlsio_options.h"



//#define MBED_TLS_DEBUG_ENABLE

static const char* OPTION_X509_CERT = "x509certificate";
static const char* OPTION_X509_PRIVATE_KEY = "x509privatekey";

typedef enum IO_STATE_TAG
{
    IO_STATE_CLOSED,
    IO_STATE_OPENING,
    IO_STATE_OPEN,
    IO_STATE_CLOSING,
    IO_STATE_ERROR
} IO_STATE;

typedef enum TLSIO_STATE_ENUM_TAG
{
    TLSIO_STATE_NOT_OPEN,
    TLSIO_STATE_OPENING_UNDERLYING_IO,
    TLSIO_STATE_IN_HANDSHAKE,
    TLSIO_STATE_OPEN,
    TLSIO_STATE_CLOSING,
    TLSIO_STATE_ERROR
} TLSIO_STATE_ENUM;

#define SOCKET_SUCCESS                 0
#define INVALID_SOCKET                 -1
#define MAC_ADDRESS_STRING_LENGTH      18

#define RECEIVE_BYTES_VALUE     64
#define CONNECT_TIMEOUT         10

typedef struct PENDING_SOCKET_IO_TAG
{
    unsigned char* bytes;
    size_t size;
    ON_SEND_COMPLETE on_send_complete;
    void* callback_context;
    SINGLYLINKEDLIST_HANDLE pending_io_list;
} PENDING_SOCKET_IO;

typedef struct TLS_IO_INSTANCE_TAG
{
    /* tls layer */
    ON_BYTES_RECEIVED on_bytes_received;
    ON_IO_OPEN_COMPLETE on_io_open_complete;
    ON_IO_CLOSE_COMPLETE on_io_close_complete;
    ON_IO_ERROR on_io_error;
    void* on_bytes_received_context;
    void* on_io_open_complete_context;
    void* on_io_close_complete_context;
    void* on_io_error_context;
    TLSIO_STATE_ENUM tlsio_state;
    unsigned char* socket_io_read_bytes;
    size_t socket_io_read_byte_count;
    ON_SEND_COMPLETE on_send_complete;
    void* on_send_complete_callback_context;
    mbedtls_entropy_context    entropy;
    mbedtls_ctr_drbg_context   ctr_drbg;
    mbedtls_ssl_context        ssl;
    mbedtls_ssl_config         config;
    mbedtls_x509_crt           trusted_certificates_parsed;
    mbedtls_ssl_session        ssn;
    mbedtls_x509_crt           client_certificates_parsed;
    mbedtls_pk_context         pk;
    char*                      trusted_certificates;
    char*                      x509certificate;
    char*                      x509privatekey;

    /* socket layer */
    IO_STATE io_state;
    //SINGLYLINKEDLIST_HANDLE pending_io_list;
    int socket;
    char* hostname;
    int port;

} TLS_IO_INSTANCE;

static const IO_INTERFACE_DESCRIPTION tlsio_mbedtls_interface_description =
{
    tlsio_mbedtls_retrieveoptions,
    tlsio_mbedtls_create,
    tlsio_mbedtls_destroy,
    tlsio_mbedtls_open,
    tlsio_mbedtls_close,
    tlsio_mbedtls_send,
    tlsio_mbedtls_dowork,
    tlsio_mbedtls_setoption
};

#if defined (MBED_TLS_DEBUG_ENABLE)
void mbedtls_debug(void *ctx, int level, const char *file, int line, const char *str)
{
    ((void)level);
    printf("%s (%d): %s\r\n", file, line, str);
}
#endif

static void indicate_error(TLS_IO_INSTANCE* tls_io_instance)
{
    if (tls_io_instance->on_io_error != NULL)
    {
        tls_io_instance->on_io_error(tls_io_instance->on_io_error_context);
    }
}

static void indicate_open_complete(TLS_IO_INSTANCE* tls_io_instance, IO_OPEN_RESULT open_result)
{
    if (tls_io_instance->on_io_open_complete != NULL)
    {
        tls_io_instance->on_io_open_complete(tls_io_instance->on_io_open_complete_context, open_result);
    }
}

static int decode_ssl_received_bytes(TLS_IO_INSTANCE* tls_io_instance)
{
    int result = 0;
    unsigned char buffer[64];
    int rcv_bytes = 1;

    while (rcv_bytes > 0)
    {
        rcv_bytes = mbedtls_ssl_read(&tls_io_instance->ssl, buffer, sizeof(buffer));
        if (rcv_bytes > 0)
        {
            if (tls_io_instance->on_bytes_received != NULL)
            {
                tls_io_instance->on_bytes_received(tls_io_instance->on_bytes_received_context, buffer, rcv_bytes);
            }
        }
    }

    return result;
}

int socket_close(TLS_IO_INSTANCE* tls_io_instance, ON_IO_CLOSE_COMPLETE on_io_close_complete, void* callback_context);

static void on_underlying_io_open_complete(void* context, IO_OPEN_RESULT open_result)
{
    TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)context;
    int result = 0;

    if (open_result != IO_OPEN_OK)
    {
        printf("[%s, %d]: find err  \n", __FILE__, __LINE__);
        socket_close(tls_io_instance, NULL, NULL);
        tls_io_instance->tlsio_state = TLSIO_STATE_NOT_OPEN;
        indicate_open_complete(tls_io_instance, IO_OPEN_ERROR);
    }
    else
    {
        tls_io_instance->tlsio_state = TLSIO_STATE_IN_HANDSHAKE;

        printf("start to do handshake!\n");

#ifdef CONFIG_TARGET_PLATFORM_ESP8266
    printf("set CPU to 160M, enter handshake...\r\n");
    rtc_clk_cpu_freq_set(RTC_CPU_FREQ_160M);
#endif

        do {
            result = mbedtls_ssl_handshake(&tls_io_instance->ssl);
        } while (result == MBEDTLS_ERR_SSL_WANT_READ || result == MBEDTLS_ERR_SSL_WANT_WRITE);

#ifdef CONFIG_TARGET_PLATFORM_ESP8266
    printf("exit handshake,set CPU to 80M\r\n");
    rtc_clk_cpu_freq_set(RTC_CPU_FREQ_80M);
#endif

        if (result == 0)
        {
            printf("handshake success!!\n");
            tls_io_instance->tlsio_state = TLSIO_STATE_OPEN;
            indicate_open_complete(tls_io_instance, IO_OPEN_OK);
        }
        else
        {
            printf("handshake fail!!\n");
            socket_close(tls_io_instance, NULL, NULL);
            tls_io_instance->tlsio_state = TLSIO_STATE_NOT_OPEN;
            indicate_open_complete(tls_io_instance, IO_OPEN_ERROR);
        }
    }
}

static void on_underlying_io_bytes_received(void* context, const unsigned char* buffer, size_t size)
{
    TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)context;

    unsigned char* new_socket_io_read_bytes = (unsigned char*)realloc(tls_io_instance->socket_io_read_bytes, tls_io_instance->socket_io_read_byte_count + size);

    if (new_socket_io_read_bytes == NULL)
    {
        tls_io_instance->tlsio_state = TLSIO_STATE_ERROR;
        indicate_error(tls_io_instance);
    }
    else
    {
        tls_io_instance->socket_io_read_bytes = new_socket_io_read_bytes;
        (void)memcpy(tls_io_instance->socket_io_read_bytes + tls_io_instance->socket_io_read_byte_count, buffer, size);
        tls_io_instance->socket_io_read_byte_count += size;
    }
}

static void on_underlying_io_error(void* context)
{
    TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)context;

    switch (tls_io_instance->tlsio_state)
    {
    default:
    case TLSIO_STATE_NOT_OPEN:
    case TLSIO_STATE_ERROR:
        break;

    case TLSIO_STATE_OPENING_UNDERLYING_IO:
    case TLSIO_STATE_IN_HANDSHAKE:
        socket_close(tls_io_instance, NULL, NULL);
        tls_io_instance->tlsio_state = TLSIO_STATE_NOT_OPEN;
        printf("[%s, %d]: find err  \n", __FILE__, __LINE__);
        indicate_open_complete(tls_io_instance, IO_OPEN_ERROR);
        break;

    case TLSIO_STATE_OPEN:
        tls_io_instance->tlsio_state = TLSIO_STATE_ERROR;
        indicate_error(tls_io_instance);
        break;
    }
}

static void on_underlying_io_close_complete_during_close(void* context)
{
    TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)context;

    tls_io_instance->tlsio_state = TLSIO_STATE_NOT_OPEN;

    if (tls_io_instance->on_io_close_complete != NULL)
    {
        tls_io_instance->on_io_close_complete(tls_io_instance->on_io_close_complete_context);
    }
}


void socket_dowork(TLS_IO_INSTANCE* tls_io_instance)
{
    if (tls_io_instance->io_state == IO_STATE_OPEN)
    {
        int received = 1;
#if 0
        LIST_ITEM_HANDLE first_pending_io = singlylinkedlist_get_head_item(tls_io_instance->pending_io_list);
        while (first_pending_io != NULL)
        {
            PENDING_SOCKET_IO* pending_socket_io = (PENDING_SOCKET_IO*)singlylinkedlist_item_get_value(first_pending_io);
            if (pending_socket_io == NULL)
            {
                tls_io_instance->io_state = IO_STATE_ERROR;
                on_underlying_io_error(tls_io_instance);
                printf("Failure: retrieving socket from list\n");
                break;
            }

            int send_result = send(tls_io_instance->socket, pending_socket_io->bytes, pending_socket_io->size, 0);
            if (send_result != pending_socket_io->size)
            {
                if (send_result == INVALID_SOCKET)
                {
                    if (errno == EAGAIN) /*send says "come back later" with EAGAIN - likely the socket buffer cannot accept more data*/
                    {
                        /*do nothing until next dowork */
                        break;
                    }
                    else
                    {
                        free(pending_socket_io->bytes);
                        free(pending_socket_io);
                        (void)singlylinkedlist_remove(tls_io_instance->pending_io_list, first_pending_io);

                        printf("Failure: sending Socket information. errno=%d (%s).\n", errno, strerror(errno));
                        tls_io_instance->io_state = IO_STATE_ERROR;
                        on_underlying_io_error(tls_io_instance);
                    }
                }
                else
                {
                    /* simply wait until next dowork */
                    memmove(pending_socket_io->bytes, pending_socket_io->bytes + send_result, pending_socket_io->size - send_result);
                    pending_socket_io->size -= send_result;
                    break;
                }
            }
            else
            {
                if (pending_socket_io->on_send_complete != NULL)
                {
                    pending_socket_io->on_send_complete(pending_socket_io->callback_context, IO_SEND_OK);
                }

                free(pending_socket_io->bytes);
                free(pending_socket_io);
                if (singlylinkedlist_remove(tls_io_instance->pending_io_list, first_pending_io) != 0)
                {
                    tls_io_instance->io_state = IO_STATE_ERROR;
                    on_underlying_io_error(tls_io_instance);
                    printf("Failure: unable to remove socket from list\n");
                }
            }

            first_pending_io = singlylinkedlist_get_head_item(tls_io_instance->pending_io_list);
        }
#endif
        while (received > 0)
        {
            unsigned char recv_bytes[64];

            if (recv_bytes == NULL)
            {
                printf("Socket_Failure: NULL allocating input buffer.\n");
                on_underlying_io_error(tls_io_instance);
            }
            else
            {
                received = recv(tls_io_instance->socket, recv_bytes, sizeof(recv_bytes), 0);
                if (received > 0)
                {
                    on_underlying_io_bytes_received(tls_io_instance, recv_bytes, received);
                }
            }
        }
    }

}

static int on_io_recv(void *context, unsigned char *buf, size_t sz)
{
    int result;
    TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)context;
    unsigned char* new_socket_io_read_bytes;

    while (tls_io_instance->socket_io_read_byte_count == 0)
    {
        socket_dowork(tls_io_instance);
        if (tls_io_instance->tlsio_state == TLSIO_STATE_OPEN)
        {
            break;
        }
    }

    result = tls_io_instance->socket_io_read_byte_count;
    if (result > sz)
    {
        result = sz;
    }

    if (result > 0)
    {
        (void)memcpy((void *)buf, tls_io_instance->socket_io_read_bytes, result);
        (void)memmove(tls_io_instance->socket_io_read_bytes, tls_io_instance->socket_io_read_bytes + result, tls_io_instance->socket_io_read_byte_count - result);
        tls_io_instance->socket_io_read_byte_count -= result;
        if (tls_io_instance->socket_io_read_byte_count > 0)
        {
            new_socket_io_read_bytes = (unsigned char*)realloc(tls_io_instance->socket_io_read_bytes, tls_io_instance->socket_io_read_byte_count);
            if (new_socket_io_read_bytes != NULL)
            {
                tls_io_instance->socket_io_read_bytes = new_socket_io_read_bytes;
            }
        }
        else
        {
            free(tls_io_instance->socket_io_read_bytes);
            tls_io_instance->socket_io_read_bytes = NULL;
        }
    }


    if ((result == 0) && (tls_io_instance->tlsio_state == TLSIO_STATE_OPEN))
    {
        result = MBEDTLS_ERR_SSL_WANT_READ;
    }

    return result;
}


#if 0
static int add_pending_io(TLS_IO_INSTANCE* tls_io_instance, const unsigned char* buffer, size_t size, ON_SEND_COMPLETE on_send_complete, void* callback_context)
{
    int result;
    PENDING_SOCKET_IO* pending_socket_io = (PENDING_SOCKET_IO*)malloc(sizeof(PENDING_SOCKET_IO));
    if (pending_socket_io == NULL)
    {
        result = __FAILURE__;
    }
    else
    {
        pending_socket_io->bytes = (unsigned char*)malloc(size);
        if (pending_socket_io->bytes == NULL)
        {
            printf("Allocation Failure: Unable to allocate pending list.\n");
            free(pending_socket_io);
            result = __FAILURE__;
        }
        else
        {
            pending_socket_io->size = size;
            pending_socket_io->on_send_complete = on_send_complete;
            pending_socket_io->callback_context = callback_context;
            pending_socket_io->pending_io_list = tls_io_instance->pending_io_list;
            (void)memcpy(pending_socket_io->bytes, buffer, size);

            if (singlylinkedlist_add(tls_io_instance->pending_io_list, pending_socket_io) == NULL)
            {
                printf("Failure: Unable to add socket to pending list.\n");
                free(pending_socket_io->bytes);
                free(pending_socket_io);
                result = __FAILURE__;
            }
            else
            {
                result = 0;
            }
        }
    }

    return result;
}
#endif

int socket_send(TLS_IO_INSTANCE* tls_io_instance, const void* buffer, size_t size, ON_SEND_COMPLETE on_send_complete, void* callback_context)
{
    int result = 0;

    if ((buffer == NULL) ||
        (size == 0))
    {
        printf("Invalid argument: send given invalid parameter\n");
        result = __FAILURE__;
    }
    else
    {
        if (tls_io_instance->io_state != IO_STATE_OPEN)
        {
            printf("Failure: socket state is not opened.\n");
            result = __FAILURE__;
        }
        else
        {

            int send_result = send(tls_io_instance->socket, buffer, size, 0);
            if (send_result != size)
            {
                if (send_result == INVALID_SOCKET)
                {
                    if (errno == EAGAIN) /*send says "come back later" with EAGAIN - likely the socket buffer cannot accept more data*/
                    {
                        /*do nothing*/
                        printf("[%s, %d]: find err  \n", __FILE__, __LINE__);
                        result = 0;
                    }
                    else
                    {
                        printf("size: %d, send_result: %d\n", size, send_result);
                        on_underlying_io_error(tls_io_instance);
                        printf("Failure: sending socket failed. errno=%d (%s).\n", errno, strerror(errno));
                        result = __FAILURE__;
                    }
                }
                else
                {

                    /* queue data */
                    printf("send_result != size, don't pending io\n");
                }
            }
            else
            {
                if (on_send_complete != NULL)
                {
                    on_send_complete(callback_context, IO_SEND_OK);
                }

                result = 0;
            }

        }
    }
    return result;
}

static int on_io_send(void *context, const unsigned char *buf, size_t sz)
{
    int result;
    TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)context;
#if 0
    if (socket_send(tls_io_instance, buf, sz, tls_io_instance->on_send_complete, tls_io_instance->on_send_complete_callback_context) != 0)
    {
        tls_io_instance->tlsio_state = TLSIO_STATE_ERROR;
        indicate_error(tls_io_instance);
        result = 0;
    }
    else
    {
        result = sz;
    }

    return result;
#endif



    int send_result = send(tls_io_instance->socket, buf, sz, 0);
    if (send_result != sz)
    {
        if (send_result == INVALID_SOCKET)
        {
            if (errno == EAGAIN) /*send says "come back later" with EAGAIN - likely the socket buffer cannot accept more data*/
            {
                /*do nothing*/
                printf("[%s, %d]: find err  \n", __FILE__, __LINE__);
                return 0;
            }
            else
            {
                printf("sz: %d, send_result: %d\n", sz, send_result);
                //on_underlying_io_error(tls_io_instance);
                printf("Failure: sending socket failed. errno=%d (%s).\n", errno, strerror(errno));
                //result = __FAILURE__;

                return 0;
            }
        }
        else
        {

            /* queue data */
            printf("send_result != sz, don't pending io\n");
        }
    }
    else
    {
        if (tls_io_instance->on_send_complete != NULL)
        {
            tls_io_instance->on_send_complete(tls_io_instance->on_send_complete_callback_context, IO_SEND_OK);
        }

    }



    return sz;


}

static int tlsio_entropy_poll(void *v, unsigned char *output, size_t len, size_t *olen)
{
    srand(time(NULL));
    char *c = (char*)malloc(len);
    memset(c, 0, len);
    for (uint16_t i = 0; i < len; i++) {
        c[i] = rand() % 256;
    }
    memmove(output, c, len);
    *olen = len;

    free(c);
    return(0);
}

static void mbedtls_init(void *instance, const char *host)
{
    TLS_IO_INSTANCE *result = (TLS_IO_INSTANCE *)instance;
    char *pers = "azure_iot_client";

    mbedtls_entropy_init(&result->entropy);
    mbedtls_ctr_drbg_init(&result->ctr_drbg);
    mbedtls_ssl_init(&result->ssl);
    mbedtls_ssl_session_init(&result->ssn);
    mbedtls_ssl_config_init(&result->config);
    mbedtls_x509_crt_init(&result->trusted_certificates_parsed);
    mbedtls_x509_crt_init(&result->client_certificates_parsed);
    mbedtls_pk_init( &result->pk);
    mbedtls_entropy_add_source(&result->entropy, tlsio_entropy_poll, NULL, 128, 0);
    mbedtls_ctr_drbg_seed(&result->ctr_drbg, mbedtls_entropy_func, &result->entropy, (const unsigned char *)pers, strlen(pers));
    mbedtls_ssl_config_defaults(&result->config, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
    mbedtls_ssl_conf_rng(&result->config, mbedtls_ctr_drbg_random, &result->ctr_drbg);
    mbedtls_ssl_conf_authmode(&result->config, MBEDTLS_SSL_VERIFY_REQUIRED);
    //mbedtls_ssl_conf_authmode(&result->config, MBEDTLS_SSL_VERIFY_NONE);
    mbedtls_ssl_conf_min_version(&result->config, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3);          // v1.2
    mbedtls_ssl_set_bio(&result->ssl, instance, on_io_send, on_io_recv, NULL);
    mbedtls_ssl_set_hostname(&result->ssl, host);
    mbedtls_ssl_set_session(&result->ssl, &result->ssn);

#if defined (MBED_TLS_DEBUG_ENABLE)
    mbedtls_ssl_conf_dbg(&result->config, mbedtls_debug, stdout);
    mbedtls_debug_set_threshold(3);
#endif

    mbedtls_ssl_setup(&result->ssl, &result->config);
}



CONCRETE_IO_HANDLE tlsio_mbedtls_create(void* io_create_parameters)
{
    TLSIO_CONFIG* tls_io_config = io_create_parameters;
    TLS_IO_INSTANCE* result;

    if (tls_io_config == NULL)
    {
        LogError("NULL tls_io_config\n");
        return NULL;
    }

    result = malloc(sizeof(TLS_IO_INSTANCE));
    if (result == NULL)
    {
        LogError("malloc error\n");
        return NULL;
    }

    result->on_bytes_received = NULL;
    result->on_bytes_received_context = NULL;

    result->on_io_open_complete = NULL;
    result->on_io_open_complete_context = NULL;

    result->on_io_close_complete = NULL;
    result->on_io_close_complete_context = NULL;

    result->on_io_error = NULL;
    result->on_io_error_context = NULL;

    result->trusted_certificates = NULL;
    result->x509certificate = NULL;
    result->x509privatekey = NULL;

    if (tls_io_config->hostname == NULL)
    {
        printf("hostname is null\n");
        free(result);
        return NULL;
    }

    result->hostname = (char*)malloc(strlen(tls_io_config->hostname) + 1);
    if (result->hostname == NULL)
    {
        printf("malloc falied\n");
        free(result);
        return NULL;
    }

    strcpy(result->hostname, tls_io_config->hostname);
    result->port = tls_io_config->port;

    result->socket = INVALID_SOCKET;
    result->io_state = IO_STATE_CLOSED;

    result->socket_io_read_bytes = NULL;
    result->socket_io_read_byte_count = 0;
    result->on_send_complete = NULL;
    result->on_send_complete_callback_context = NULL;

    // mbeTLS initialize
    mbedtls_init((void *)result, tls_io_config->hostname);
    result->tlsio_state = TLSIO_STATE_NOT_OPEN;
    printf("init mbedtls OK!\n");

    return result;
}


int socket_close(TLS_IO_INSTANCE* tls_io_instance, ON_IO_CLOSE_COMPLETE on_io_close_complete, void* callback_context)
{
    int result = 0;

    if ((tls_io_instance->io_state != IO_STATE_CLOSED) && (tls_io_instance->io_state != IO_STATE_CLOSING))
    {
        close(tls_io_instance->socket);
        tls_io_instance->socket = INVALID_SOCKET;
        tls_io_instance->io_state = IO_STATE_CLOSED;
    }

    if (on_io_close_complete != NULL)
    {
        on_io_close_complete(callback_context);
    }

    result = 0;
    return result;
}


void socket_destroy(TLS_IO_INSTANCE* tls_io_instance)
{
    if (tls_io_instance->socket != INVALID_SOCKET)
    {
        close(tls_io_instance->socket);
    }
}

void tlsio_mbedtls_destroy(CONCRETE_IO_HANDLE tls_io)
{
    if (tls_io != NULL)
    {
        TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;

        mbedtls_ssl_close_notify(&tls_io_instance->ssl);
        mbedtls_ssl_free(&tls_io_instance->ssl);
        mbedtls_ssl_config_free(&tls_io_instance->config);
        mbedtls_x509_crt_free(&tls_io_instance->trusted_certificates_parsed);
        mbedtls_x509_crt_free(&tls_io_instance->client_certificates_parsed);
        mbedtls_pk_free(&tls_io_instance->pk);
        mbedtls_ctr_drbg_free(&tls_io_instance->ctr_drbg);
        mbedtls_entropy_free(&tls_io_instance->entropy);

        socket_close(tls_io_instance, NULL, NULL);

        if (tls_io_instance->socket_io_read_bytes != NULL)
        {
            free(tls_io_instance->socket_io_read_bytes);
        }

        socket_destroy(tls_io_instance);
        if (tls_io_instance->trusted_certificates != NULL)
        {
            //free(tls_io_instance->trusted_certificates);
            tls_io_instance->trusted_certificates = NULL;
        }
        if(tls_io_instance->x509certificate != NULL)
        {
            //free(tls_io_instance->x509certificate);
            tls_io_instance->x509certificate = NULL;
        }
        if(tls_io_instance->x509privatekey != NULL)
        {
            //free(tls_io_instance->x509privatekey);
            tls_io_instance->x509privatekey = NULL;
        }

        free(tls_io);
    }
}

int socket_open(TLS_IO_INSTANCE* tls_io_instance)
{
    int result = 0;
    int retval = -1;
    int select_errno = 0;


    if (tls_io_instance->io_state != IO_STATE_CLOSED)
    {
        printf("Failure: socket state is not closed.\n");
        result = __FAILURE__;
    }
    else if (tls_io_instance->socket != INVALID_SOCKET)
    {
        tls_io_instance->io_state = IO_STATE_OPEN;

        result = 0;
    }
    else
    {
        struct addrinfo* addrInfo;
        char portString[16];

        tls_io_instance->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

        printf("create socket!\n");
        if (tls_io_instance->socket < SOCKET_SUCCESS)
        {
            printf("Failure: socket create failure %d.\n", tls_io_instance->socket);
            result = __FAILURE__;
        }
        else
        {
            struct addrinfo addrHint = { 0 };
            addrHint.ai_family = AF_INET;
            addrHint.ai_socktype = SOCK_STREAM;
            addrHint.ai_protocol = 0;

            sprintf(portString, "%u", tls_io_instance->port);
            int err = getaddrinfo(tls_io_instance->hostname, portString, &addrHint, &addrInfo);
            if (err != 0)
            {
                printf("Failure: getaddrinfo failure %d.\n", err);
                close(tls_io_instance->socket);
                tls_io_instance->socket = INVALID_SOCKET;
                result = __FAILURE__;
            }
            else
            {
                int flags;
                if ((-1 == (flags = fcntl(tls_io_instance->socket, F_GETFL, 0))) ||
                    (fcntl(tls_io_instance->socket, F_SETFL, flags | O_NONBLOCK) == -1))
                {
                    printf("Failure: fcntl failure.\n");
                    close(tls_io_instance->socket);
                    tls_io_instance->socket = INVALID_SOCKET;
                    result = __FAILURE__;
                }
                else
                {
                    printf("connecting to %s %s\n", tls_io_instance->hostname, portString);
                    err = connect(tls_io_instance->socket, addrInfo->ai_addr, sizeof(*addrInfo->ai_addr));
                    if ((err != 0) && (errno != EINPROGRESS))
                    {
                        printf("connect failure!!! errno: %d.\n", errno);
                        close(tls_io_instance->socket);
                        tls_io_instance->socket = INVALID_SOCKET;
                        result = __FAILURE__;
                    }
                    else
                    {
                        if (err != 0)
                        {
                            fd_set fdset;
                            struct timeval tv;

                            FD_ZERO(&fdset);
                            FD_SET(tls_io_instance->socket, &fdset);
                            tv.tv_sec = CONNECT_TIMEOUT;
                            tv.tv_usec = 0;

                            do
                            {
                                retval = select(tls_io_instance->socket + 1, NULL, &fdset, NULL, &tv);

                                if (retval < 0)
                                {
                                    select_errno = errno;
                                }
                            } while (retval < 0 && select_errno == EINTR);

                            if (retval != 1)
                            {
                                printf("Failure: select failure.\n");
                                close(tls_io_instance->socket);
                                tls_io_instance->socket = INVALID_SOCKET;
                                result = __FAILURE__;
                            }
                            else
                            {
                                int so_error = 0;
                                socklen_t len = sizeof(so_error);
                                err = getsockopt(tls_io_instance->socket, SOL_SOCKET, SO_ERROR, &so_error, &len);
                                if (err != 0)
                                {
                                    printf("Failure: getsockopt failure %d.\n", errno);
                                    close(tls_io_instance->socket);
                                    tls_io_instance->socket = INVALID_SOCKET;
                                    result = __FAILURE__;
                                }
                                else if (so_error != 0)
                                {
                                    err = so_error;
                                    printf("Failure: connect failure %d.\n", so_error);
                                    close(tls_io_instance->socket);
                                    tls_io_instance->socket = INVALID_SOCKET;
                                    result = __FAILURE__;
                                }
                            }
                        }
                        if (err == 0)
                        {
                            printf("socket connect OK!!\n");
                            tls_io_instance->io_state = IO_STATE_OPEN;

                            result = 0;
                        }
                    }
                }
                freeaddrinfo(addrInfo);

            }
        }
    }

    on_underlying_io_open_complete(tls_io_instance, result == 0 ? IO_OPEN_OK : IO_OPEN_ERROR);

    return result;
}

int tlsio_mbedtls_open(CONCRETE_IO_HANDLE tls_io, ON_IO_OPEN_COMPLETE on_io_open_complete, void* on_io_open_complete_context, \
    ON_BYTES_RECEIVED on_bytes_received, void* on_bytes_received_context, \
    ON_IO_ERROR on_io_error, void* on_io_error_context)
{
    int result = 0;

    if (tls_io == NULL)
    {
        LogError("NULL tls_io");
        result = __FAILURE__;
    }
    else
    {
        TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;

        if (tls_io_instance->tlsio_state != TLSIO_STATE_NOT_OPEN)
        {
            LogError("IO should not be open: %d\n", tls_io_instance->tlsio_state);
            result = __FAILURE__;
        }
        else
        {
            tls_io_instance->on_bytes_received = on_bytes_received;
            tls_io_instance->on_bytes_received_context = on_bytes_received_context;

            tls_io_instance->on_io_open_complete = on_io_open_complete;
            tls_io_instance->on_io_open_complete_context = on_io_open_complete_context;

            tls_io_instance->on_io_error = on_io_error;
            tls_io_instance->on_io_error_context = on_io_error_context;

            tls_io_instance->tlsio_state = TLSIO_STATE_OPENING_UNDERLYING_IO;

            if (socket_open(tls_io_instance) != 0)
            {
                LogError("Underlying IO open failed\n");
                tls_io_instance->tlsio_state = TLSIO_STATE_NOT_OPEN;
                result = __FAILURE__;
            }
            else
            {
                result = 0;
            }
        }
    }

    return result;
}

int tlsio_mbedtls_close(CONCRETE_IO_HANDLE tls_io, ON_IO_CLOSE_COMPLETE on_io_close_complete, void* callback_context)
{
    int result = 0;

    if (tls_io == NULL)
    {
        result = __FAILURE__;
    }
    else
    {
        TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;

        if ((tls_io_instance->tlsio_state == TLSIO_STATE_NOT_OPEN) ||
            (tls_io_instance->tlsio_state == TLSIO_STATE_CLOSING))
        {
            result = __FAILURE__;
        }
        else
        {
            tls_io_instance->tlsio_state = TLSIO_STATE_CLOSING;
            tls_io_instance->on_io_close_complete = on_io_close_complete;
            tls_io_instance->on_io_close_complete_context = callback_context;

            if (socket_close(tls_io_instance,
                on_underlying_io_close_complete_during_close, tls_io_instance) != 0)
            {
                result = __FAILURE__;
            }
            else
            {
                result = 0;
            }
        }
    }

    return result;
}

int tlsio_mbedtls_send(CONCRETE_IO_HANDLE tls_io, const void* buffer, size_t size, ON_SEND_COMPLETE on_send_complete, void* callback_context)
{
    int result;

    if (tls_io == NULL)
    {
        result = __FAILURE__;
    }
    else
    {
        TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;

        if (tls_io_instance->tlsio_state != TLSIO_STATE_OPEN)
        {
            result = __FAILURE__;
        }
        else
        {
            tls_io_instance->on_send_complete = on_send_complete;
            tls_io_instance->on_send_complete_callback_context = callback_context;

            int res = mbedtls_ssl_write(&tls_io_instance->ssl, buffer, size);
            if (res != size)
            {
                result = __FAILURE__;
            }
            else
            {
                result = 0;
            }
        }
    }

    return result;
}

void tlsio_mbedtls_dowork(CONCRETE_IO_HANDLE tls_io)
{
    if (tls_io != NULL)
    {
        TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;

        if ((tls_io_instance->tlsio_state != TLSIO_STATE_NOT_OPEN) &&
            (tls_io_instance->tlsio_state != TLSIO_STATE_ERROR))
        {
            decode_ssl_received_bytes(tls_io_instance);
            socket_dowork(tls_io_instance);
        }
    }
}

const IO_INTERFACE_DESCRIPTION* tlsio_mbedtls_get_interface_description(void)
{
    return &tlsio_mbedtls_interface_description;
}

static void* tlsio_mbedtls_CloneOption(const char* name, const void* value)
{
    void* result;
    if (
        (name == NULL) || (value == NULL)
        )
    {
        LogError("invalid parameter detected: const char* name=%p, const void* value=%p\n", name, value);
        result = NULL;
    }
    else
    {
        if (strcmp(name, OPTION_TRUSTED_CERT) == 0 ||
            strcmp(name, SU_OPTION_X509_CERT) == 0 ||
            strcmp(name, SU_OPTION_X509_PRIVATE_KEY) == 0)
        {
            if (mallocAndStrcpy_s((char**)&result, value) != 0)
            {
                LogError("unable to mallocAndStrcpy_s TrustedCerts value\n");
                result = NULL;
            }
            else
            {
                /*return as is*/
            }
        }
        else
        {
            LogError("not handled option : %s\n", name);
            result = NULL;
        }
    }
    return result;
}

static void tlsio_mbedtls_DestroyOption(const char* name, const void* value)
{
    if (name == NULL || value == NULL)
    {
        LogError("invalid parameter detected: const char* name=%p, const void* value=%p\n", name, value);
    }
    else
    {
        if (strcmp(name, OPTION_TRUSTED_CERT) == 0 ||
            strcmp(name, SU_OPTION_X509_CERT) == 0 ||
            strcmp(name, SU_OPTION_X509_PRIVATE_KEY) == 0)
        {
            free((void*)value);
        }
        else
        {
            LogError("not handled option : %s\n", name);
        }
    }
}


/*this function will clone an option given by name and value*/
static void* socket_CloneOption(const char* name, const void* value)
{
    void* result;

    if (name != NULL)
    {
        result = NULL;

        if (strcmp(name, OPTION_TRUSTED_CERT) == 0 ||
            strcmp(name, SU_OPTION_X509_CERT) == 0 ||
            strcmp(name, SU_OPTION_X509_PRIVATE_KEY) == 0)
        {
            if (value == NULL)
            {
                printf("Failed cloning option %s (value is NULL)", name);
            }
            else
            {
                if ((result = malloc(sizeof(char) * (strlen((char*)value) + 1))) == NULL)
                {
                    printf("Failed cloning option %s (malloc failed)", name);
                }
                else if (strcpy((char*)result, (char*)value) == NULL)
                {
                    printf("Failed cloning option %s (strcpy failed)", name);
                    free(result);
                    result = NULL;
                }
            }
        }
        else
        {
            printf("Cannot clone option %s (not suppported)", name);
        }
    }

    return result;
}

static void socket_DestroyOption(const char* name, const void* value)
{
    if (name != NULL)
    {
        if ((strcmp(name, OPTION_TRUSTED_CERT) == 0 ||
            strcmp(name, SU_OPTION_X509_CERT) == 0 ||
            strcmp(name, SU_OPTION_X509_PRIVATE_KEY) == 0) && value != NULL)
        {
            free((void*)value);
        }
        else
        {
            printf("Cannot destroy option %s (not suppported)", name);
        }
    }
}


OPTIONHANDLER_HANDLE tlsio_mbedtls_retrieveoptions(CONCRETE_IO_HANDLE handle)
{
    TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)handle;

    OPTIONHANDLER_HANDLE result;
    if (tls_io_instance == NULL)
    {
        LogError("NULL tlsio");
        result = NULL;
    }
    else
    {
        TLSIO_OPTIONS options;
        options.trusted_certs = tls_io_instance->trusted_certificates;
        options.x509_cert     = tls_io_instance->x509certificate;
        options.x509_key      = tls_io_instance->x509privatekey;
        result = tlsio_options_retrieve_options(&options, tlsio_mbedtls_setoption);
    }
    return result;
}


int socket_setoption(void* tls_io, const char* optionName, const void* value)
{
    int result;

    if (optionName == NULL ||
        value == NULL)
    {
        result = __FAILURE__;
    }
    else
    {
        TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;

        if (strcmp(optionName, "tcp_keepalive") == 0)
        {
            result = setsockopt(tls_io_instance->socket, SOL_SOCKET, SO_KEEPALIVE, value, sizeof(int));
            if (result == -1) result = errno;
        }
        else
        {
            result = __FAILURE__;
        }
    }

    return result;
}

int tlsio_mbedtls_setoption(CONCRETE_IO_HANDLE tls_io, const char* optionName, const void* value)
{
    int result;

    if (tls_io == NULL || optionName == NULL)
    {
        result = __FAILURE__;
    }
    else
    {
        TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;

        if (strcmp(OPTION_TRUSTED_CERT, optionName) == 0)
        {
            if (tls_io_instance->trusted_certificates != NULL)
            {
                //free(tls_io_instance->trusted_certificates);
                tls_io_instance->trusted_certificates = NULL;
            }

            tls_io_instance->trusted_certificates = (char*)value;
            int parse_result = mbedtls_x509_crt_parse(&tls_io_instance->trusted_certificates_parsed, (const unsigned char *)value, (int)(strlen(value) + 1));
            if (parse_result != 0)
            {
                LogInfo("Malformed pem certificate\n");
                result = __FAILURE__;
            }
            else
            {
                mbedtls_ssl_conf_ca_chain(&tls_io_instance->config, &tls_io_instance->trusted_certificates_parsed, NULL);
                result = 0;
            }

        }
        else if (strcmp(OPTION_X509_CERT, optionName) == 0)
        {
            if (tls_io_instance->x509certificate != NULL)
            {
                LogError("unable to set x509 options more than once\n");
                result = __FAILURE__;
            }
            else
            {

                tls_io_instance->x509certificate = (char*)value;
                int parse_result = mbedtls_x509_crt_parse(&tls_io_instance->client_certificates_parsed, (const unsigned char *)value, (int)(strlen(value) + 1));
                if (parse_result != 0)
                {
                    LogInfo("Malformed pem certificate\n");
                    result = __FAILURE__;
                }
                else
                {
                    if (tls_io_instance->x509privatekey != NULL && tls_io_instance->x509certificate != NULL)
                    {
                        int set_key_result = mbedtls_ssl_conf_own_cert(&tls_io_instance->config, &tls_io_instance->client_certificates_parsed, &tls_io_instance->pk);

                        if (set_key_result != 0)
                        {
                            LogInfo("Fail to set private key and certificate\n");
                            result = __FAILURE__;
                        }
                        else
                        {
                            result = 0;
                        }
                    }
                    else
                    {
                        result = 0;
                    }
                }

            }
        }
        else if (strcmp(OPTION_X509_PRIVATE_KEY, optionName) == 0)
        {
            if (tls_io_instance->x509privatekey != NULL)
            {
                LogError("unable to set more than once private key options");
                result = __FAILURE__;
            }
            else
            {

                tls_io_instance->x509privatekey = (char*)value;
                int parse_result = mbedtls_pk_parse_key(&tls_io_instance->pk, (const unsigned char *)value, (int)(strlen(value) + 1), NULL, 0);
                if (parse_result != 0)
                {
                    LogInfo("Malformed pem private key");
                    result = __FAILURE__;
                }
                else
                {
                    if (tls_io_instance->x509privatekey != NULL && tls_io_instance->x509certificate != NULL)
                    {
                        int set_key_result = mbedtls_ssl_conf_own_cert(&tls_io_instance->config, &tls_io_instance->client_certificates_parsed, &tls_io_instance->pk);

                        if (set_key_result != 0)
                        {
                            LogInfo("Fail to set private key and certificate");
                            result = __FAILURE__;
                        }
                        else
                        {
                            result = 0;
                        }
                    }
                    else
                    {
                        result = 0;
                    }
                }

            }
        }
        else
        {
            result = socket_setoption(tls_io_instance, optionName, value);
        }
    }

    return result;
}

#endif // USE_MBED_TLS
